
let config = require("./config")
let express = require("express")
let bodyParser = require("body-parser")
var path = require('path');
let router = require("./router")
let jsonParse = bodyParser.json()
let urlencoded = bodyParser.urlencoded({extended:false})
//文件
let fs = require('fs');
let appToken = require('./token_vertify.js');


let appTool = require('./common/tool')


let app = express();


/**

 socket.io先是以polling方式GET和POST一些数据，包括握手和开始的一些事件，升级到websocket后就采用websocket传输了。

 如下设置优先采用websocket

 #服务器
 var io = require('socket.io')(httpServer,{
   "serveClient": false ,
   "transports":['websocket', 'polling']
 });

 //或单独设置
 io.set("transports", ["xhr-polling", "web socket", "polling", "htmlfile"]);


 #客户端
 var socket = require('socket.io-client')('http://localhost/chat',   {
  "transports":['websocket', 'polling']
});


 const socket = io('ws://192.168.0.101:8092',{
    "transports":['websocket', 'polling']
});

socket.io包含websocket同时多封装了polling方式，另外比websocket更健壮一些，并发量要比websocket差一些

 * */

const http = require('http').Server(app);
//cors:true  允许跨域,pingInterval 心跳时间, pingTimeout 心跳超时
const io = require('socket.io')(http,{cors:true,'pingInterval':6000,'pingTimeout':10000,"transports":['websocket', 'polling']});

//根据socketid查找socket对象, 高版本不需要
// var _ = require('underscore');

//设置允许访问域的值
// io.origins(['*:*','*:17984']);

app.use(jsonParse);
app.use(urlencoded);

// 静态资源，路径要设置正确，否则访问不了
//当前js路径下的相对路径
// app.use(express.static('./static'));
//路径拼接
// app.use(express.static(path.join(__dirname, "./static")));
//增加访问url前缀
app.use("/mystatic",express.static('./static'));
// app.use("/ueditor/ue", ueditor(path.join(__dirname, 'static'), router.verifylogin));


// app.use(express.static(__dirname + 'static'));
// app.use(express.static('views'))

//接口路径
let baseURLPath = '';
let loginPath = baseURLPath + "/login";
let verifyloginPath = baseURLPath + "/verifylogin";
let registerPath = baseURLPath + "/register";
let allUsers = baseURLPath + "/allUsers";

let first = true;

// 跨域
app.all('*',function(req,res,next){
    res.header( 'Access-Control-Allow-Origin', '*' );
    res.header('Access-Control-Allow-Credentials', 'true');
    res.header('Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE');
    res.header('Access-Control-Expose-Headers', 'Content-Length');
    res.header('Access-Control-Allow-Headers', 'Accept, Authorization, Content-Type, X-Requested-With, Range, application/javascript;charset=UTF-8')
    res.header("Access-Control-Allow-Origin","*")
    res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');

    // console.log(req.path);
    // console.log(req.url);
    //
    //
    // console.log("headers = " + JSON.stringify(req.headers));// 包含了各种header，包括x-forwarded-for(如果被代理过的话)
    // console.log("x-forwarded-for = " + req.header('x-forwarded-for'));// 各阶段ip的CSV, 最左侧的是原始ip
    // console.log("ips = " + JSON.stringify(req.ips));// 相当于(req.header('x-forwarded-for') || '').split(',')
    // console.log("remoteAddress = " + req.connection.remoteAddress);// 未发生代理时，请求的ip
    // console.log("ip = " + req.ip);// 同req.connection.remoteAddress, 但是格式要好一些


    console.log(req.headers.referer + "  " + appTool.isString(req.headers.referer));

    console.log("req.path:" + req.path);

    //TODO: token处理
    if ( req.path === loginPath || req.path === verifyloginPath || req.path === registerPath )
    {
        next();
        return;
    }

    /*
    请求静态资源html时
    如果不存在该资源时，那么req.headers.referer就有值，这个时候req.path 不包含 mystatic(当前项目设置的)，
    如果存在该资源时，那么req.headers.referer不存在，req.path包含mystatic(当前项目设置的)

    请求静态资源image时情况和上面情况相反

    使用本机127.0.0.1访问图片资源，不会每次都进入当前拦截方法，可以使用内网IP地址访问
    */
    if ( !req.headers.referer && req.path.indexOf("/mystatic/") != -1 )
    {
        let token = req.headers['authorization'];
        console.log("token:"+token);
        //请求资源
        //未登录，重定向到登录页面
        // var form = fs.readFileSync('./static/index2.html', {encoding: 'utf8'});
        // res.status = 302;
        // res.send(form);


        //重定向到指定图片
        // if (first)
        // {
        //     res.redirect(302,'http://127.0.0.1:8092/mystatic/images/1.jpg');
        //     first = false;
        // }
        // else
        // {
        //     next();
        // }

        next();
        return;
    } else if (req.headers.referer && appTool.isString(req.headers.referer) && req.headers.referer.indexOf("/mystatic/") != -1 && req.path.indexOf("/mystatic/") == -1) {

        let token = req.headers['authorization'];
        console.log("token:"+token);

        //包含mystatic,表示资源，req.path 里面不包含 mystatic
        console.log("请求资源");

        //判断token，是否有权限

        //重定向到指定图片
        // if (first)
        // {
        //     res.redirect(302,'http://127.0.0.1:8092/mystatic/images/1.jpg');
        //     first = false;
        // }
        // else
        // {
        //     next();
        // }

        next();
        return;
    }

    //TODO:验证token有效性
    console.log("验证token有效性");
    let token = req.headers['authorization'];

    if (!token) {
        // console.log("未获取到token");

        return res.json({status: 301, des: "unlogin"});
        // return next();
    } else {
        // console.log("token=" + token);
        appToken.verToken(token).then((data) => {

            //用户信息赋值到请求信息里面，具体业务根据用户信息来对应操作
            req.data = data;
            //验证成功后，判断是那个用户
            console.log("token验证 账号:" + data.name);
            return next();
        }).catch((error) => {
            console.log(req.method);
            console.log("token验证异常" + error);
            // return next(error);
            return res.json({status: 301, des: "unlogin"});
        })
    }
})


// 登录
app.post(loginPath,router.login);
//兼容get,post
app.use(verifyloginPath,router.verifylogin);
app.get(allUsers,router.allUsers);
app.post(registerPath,router.register);


//express错误处理  404 自定义，代码写在所有接口定义之后，否则不存在的url不会调用下面的方法
app.get('*',function (req,res) {
    // res.send('my custom 404 not found',404);
    // res.status(404).json({"err":"404"});

    // console.log(req.url + " 404");
    // res.status(404).send('Sorry, we cannot find that!');
    // res.end();


    //读取指定文件返回
    var form = fs.readFileSync('./static/index2.html', { encoding: 'utf8' });
    res.send(form);

    // console.log("***:" + req.url);

    //404重定向到指定页面，一般重定向到登录页面
    // res.redirect(302,'http://www.baidu.com');
});
// app.post('*',function (req,res) {
//     // res.send('my custom 404 not found',404);
//     // res.status(404).json({"err":"404"});
//
//     console.log(req.url + " 404");
//     res.status(404).send('Sorry, we cannot find that!');
//     res.end();
// });

// let wsUser = {
//   sockID:'',
// };

//key:userID value: wsUser
let wsUsers = {};
//key:socketid value: userID
let wsIDS = {};

/** 知识点
 * 1、命名空间、房间
 *  https://www.dazhuanlan.com/2020/01/29/5e31407cbfe77/
 *
 *
 *  Demo或库
 *  https://github.com/CCZX/wechat
 *  https://github.com/fengli12321/Socket.io-FLSocketIM-iOS
 *  https://github.com/socketio
 *
 * 官网,内有其他语言的代码链接
 *  https://socket.io/blog/socket-io-3-release/
 *  https://socket.io/blog/socket-io-redis-adapter-6-release/
 *
 * 通过 socket.io-redis 实现分布式
*/
io.on("connection", socket => {  // 客户端链接成功

    console.log("websocket connnect:" + socket.id);

    socket.on("disconnect", async _ => {  // 客户端断开链接
        console.log("disconnect id:" + socket.id);

        let userID = wsIDS[socket.id];
        if (userID)
        {
            if (wsUsers[userID])
            {
                delete wsUsers[userID];
            }
            delete wsIDS[socket.id];
        }

        //打印房间成员，经测试客户端断开连接时，socket.io会将断开的socket退出之前加入的所有房间
        //方式1
        const ids = await io.of("/").in("100").allSockets();
        ids.forEach(item => {
            console.log('room member id：' + item)
        });

        //方式2
        // io.of("/").in('100').allSockets().then(items => {
        //
        //     //items是对象，没有length属性
        //     // console.log(items.length);
        //
        //     items.forEach(item => {
        //         console.log('room member id：' + item)
        //     })
        // });

    });

    socket.on("echo", msg => {
        console.log("id:" + socket.id);

        socket.emit("echo", msg);

        //发送到指定房间
        // io.sockets.in('room').emit('echo','room message');
    });

    //多参数，并可以设置回复
    socket.on('echo-response',(arg1,arg2,callback) => {

        console.log(arg1);
        console.log(arg2);
        callback({status:"has been recv your message"});
    });

    /**
     * 房间相关操作
    * */
    socket.on('roomMessage',msg => {
        //房间信息，类似聊天时，群组等，具体协议可细分
        let from = wsIDS[socket.id];

        let sendMsg = { roomID:msg.roomID,from:from,data:msg.data };

        //向房间发送信息，包含自己
        // io.sockets.in(msg.roomID).emit('roomMessage',msg.data + "from:" + from);

        //向房间发送信息，不包含自己
        socket.broadcast.to(msg.roomID).emit('roomMessage', sendMsg);
    });

    socket.on('loginRoom',msg => {
        //TODO： 根据msg内容加入对应的房间
        wsIDS[socket.id] = msg.userID;
        wsUsers[msg.userID] = {sockID:socket.id,ws:socket};

        //加入多个房间
        for (let i = 0; i < msg.roomIDs; i++) {
            socket.join(msg.roomIDs[i]);
        }
        // console.log(io.sockets.manager.rooms);
    });

    socket.on('leaveRoom',msg => {
        //TODO： 根据msg内容离开对应房间

        delete wsUsers[msg.roomID];

        for (let i = 0; i < msg.roomIDs; i++) {
            socket.leave(msg.roomIDs[i]);
        }
        // console.log(io.sockets.manager.rooms);
    });

    /**
     * 点对点
     * */
    socket.on('LogIn',msg => {

        wsIDS[socket.id] = msg.userID;
        wsUsers[msg.userID] = {sockID:socket.id,ws:socket};

        console.log('LogIn:' + msg.userID + ' ' + socket.id);
    });
    socket.on('sendMessage',msg => {

        console.log('sendmessage' + msg);
        if (wsUsers[msg.to])
        {
            let user = wsUsers[msg.to];
            console.log(msg.to + ' to ' + user.sockID);

            let toWS = user.ws;
            toWS.emit('receiveMessage','transfer message');
        }
    });

    //异步处理方法
    socket.on("echo1" ,async msg =>{

    });
});

//命名空间
const IMRoom = io.of('/room');
IMRoom.on('connection', function(socket) {

    console.log("IMRoom connnect:" + socket.id);

    socket.join('room');

    //使用io，会给所有连接发送消息
    // io.emit('broadcast', /* … */); // emit an event to all connected sockets

    socket.on("echo", msg => {  // 监听的频道必须和客户端监听的频道相同，等待消息
        console.log("id:" + socket.id);

        IMRoom.emit("echo", msg);  // 向当前房间内所有客户端发送信息
    });

    socket.on("disconnect", _ => {  // 客户端断开链接
        console.log("disconnect id:" + socket.id);
    });

    //异步处理方法
    socket.on("echo1" ,async msg =>{

    });
});




//启动
// 命令行这样输入
// 端口参数 PORT=9090 node app.js
// 环境参数 NODE_ENV=uat node app.js
// PORT=8899 NODE_ENV='uat' node app.js，可以编写脚本执行
http.listen(process.env.PORT || config.PortConfig.listenPort,function(){
    console.log('app port: '+ (process.env.PORT || config.PortConfig.listenPort) );
})
